/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_JS_THREAD_H
#define ECMASCRIPT_JS_THREAD_H

#include "mem/ecma_string.h"
#include "runtime/include/thread.h"

#include "include/managed_thread.h"
#include "plugins/ecmascript/runtime/ecma_global_storage.h"
#include "plugins/ecmascript/runtime/global_env_constants.h"
#include "plugins/ecmascript/runtime/mem/object_xray.h"

namespace ark::ecmascript {
class EcmaVM;
class EcmascriptEnvironment;
class InternalCallParams;
class PropertiesCache;
class JSSpanHandle;
class ScopedCallInfo;

class JSThread : public ManagedThread {
public:
    static constexpr int CONCURRENT_MARKING_BITFIELD_NUM = 2;
    using Address = uintptr_t;
    static JSThread *Cast(ManagedThread *thread)
    {
        ASSERT(thread != nullptr);
        return reinterpret_cast<JSThread *>(thread);
    }

    /**
     * @brief GetCurrentRaw Unsafe method to get current JSThread.
     * It can be used in hotspots to get the best performance.
     * We can only use this method in places where the JSThread exists.
     * @return pointer to JSThread
     */
    static JSThread *GetCurrentRaw()
    {
        ManagedThread *managedThread = ManagedThread::GetCurrent();
        ASSERT(managedThread != nullptr);
        ASSERT(managedThread->GetThreadLang() == ark::panda_file::SourceLang::ECMASCRIPT);
        return JSThread::Cast(managedThread);
    }

    /**
     * @brief GetCurrent Safe method to gets current JSThread.
     * @return pointer to JSThread or nullptr (if current thread is not a js thread)
     */
    static JSThread *GetCurrent()
    {
        ManagedThread *managedThread = ManagedThread::GetCurrent();
        if (managedThread != nullptr && managedThread->GetThreadLang() == ark::panda_file::SourceLang::ECMASCRIPT) {
            return JSThread::Cast(managedThread);
        }
        return nullptr;
    }

    JSThread(Runtime *runtime, PandaVM *vm);

    ~JSThread() override;

    PANDA_PUBLIC_API EcmaVM *GetEcmaVM() const;

    static JSThread *Create(Runtime *runtime, PandaVM *vm);

    int GetNestedLevel() const
    {
        return nestedLevel_;
    }

    void SetNestedLevel(int level)
    {
        nestedLevel_ = level;
    }

    void Iterate(const RootVisitor &v0, const RootRangeVisitor &v1);

    PANDA_PUBLIC_API uintptr_t *ExpandHandleStorage();
    PANDA_PUBLIC_API void ShrinkHandleStorage(int prevIndex);

    JSTaggedType *GetHandleScopeStorageNext() const
    {
        return handleScopeStorageNext_;
    }

    void SetHandleScopeStorageNext(JSTaggedType *value)
    {
        handleScopeStorageNext_ = value;
    }

    JSTaggedType *GetHandleScopeStorageEnd() const
    {
        return handleScopeStorageEnd_;
    }

    void SetHandleScopeStorageEnd(JSTaggedType *value)
    {
        handleScopeStorageEnd_ = value;
    }

    int GetCurrentHandleStorageIndex()
    {
        return currentHandleStorageIndex_;
    }

    void HandleScopeCountAdd()
    {
        handleScopeCount_++;
    }

    void HandleScopeCountDec()
    {
        handleScopeCount_--;
    }

    void SetException(JSTaggedValue exception);

    JSTaggedValue GetException() const
    {
        return JSTaggedValue(ManagedThread::GetException());
    }

    bool HasPendingException() const
    {
        return !JSTaggedValue(ManagedThread::GetException()).IsHole();
    }

    PANDA_PUBLIC_API void ClearException();

    EcmaGlobalStorage *GetEcmaGlobalStorage() const
    {
        return globalStorage_;
    }

    const GlobalEnvConstants *GlobalConstants() const
    {
        return &globalConst_;
    }

    JSTaggedValue GetFunctionalObject() const
    {
        return functionalObject_;
    }

    void SetFunctionalObject(JSTaggedValue functionalObject)
    {
        functionalObject_ = functionalObject;
    }

    JSTaggedValue GetInvocationLexicalEnv() const
    {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.WasteObjHeader)
        return invocationLexicalEnv_;  // GC root
    }

    void SetInvocationLexicalEnv(JSTaggedValue invocationLexicalEnv)
    {
        invocationLexicalEnv_ = invocationLexicalEnv;
    }

    void NotifyStableArrayElementsGuardians(JSHandle<JSObject> receiver);

    bool IsStableArrayElementsGuardiansInvalid() const
    {
        return !stableArrayElementsGuardians_;
    }

    void ResetGuardians();

    InternalCallParams *GetInternalCallParams() const
    {
        return internalCallParams_;
    }

    ThreadId GetThreadId() const
    {
        return GetId();
    }

    static ThreadId GetCurrentThreadId()
    {
        return os::thread::GetCurrentThreadId();
    }

    PropertiesCache *GetPropertiesCache() const
    {
        return propertiesCache_;
    }

    static constexpr uint32_t GetPropertiesCacheOffset()
    {
        return MEMBER_OFFSET(JSThread, propertiesCache_);
    }

    static constexpr uint32_t GetGlobalConstantsOffset()
    {
        return MEMBER_OFFSET(JSThread, globalConst_);
    }

    static constexpr uint32_t GetGlobalStorageOffset()
    {
        return MEMBER_OFFSET(JSThread, globalStorage_);
    }

    static constexpr uint32_t GetGlobalObjectOffset()
    {
        return MEMBER_OFFSET(JSThread, globalObj_);
    }

    void SetGlobalObject(JSTaggedValue globalObj)
    {
        globalObj_ = globalObj;
    }

    JSTaggedValue GetGlobalObject() const
    {
        return globalObj_;
    }

    void DisableStackOverflowCheck() override;
    void EnableStackOverflowCheck() override;

private:
    void IterateEcmascriptEnvironment(const RootVisitor &v0, const RootRangeVisitor &v1);

    NO_COPY_SEMANTIC(JSThread);
    NO_MOVE_SEMANTIC(JSThread);

    void DumpStack() DUMP_API_ATTR;
    bool TestLockState() const override
    {
        return true;
    }

    static constexpr uint32_t MAX_STACK_SIZE = 128 * 1024;
    static constexpr uint32_t RESERVE_STACK_SIZE = 128;
    static const uint32_t NODE_BLOCK_SIZE_LOG2 = 10;
    static const uint32_t NODE_BLOCK_SIZE = 1U << NODE_BLOCK_SIZE_LOG2;
    static constexpr int32_t MIN_HANDLE_STORAGE_SIZE = 2;

    // MM: handles, global-handles
    int nestedLevel_ = 0;
    JSTaggedType *handleScopeStorageNext_ {nullptr};
    JSTaggedType *handleScopeStorageEnd_ {nullptr};
    PandaVector<std::array<JSTaggedType, NODE_BLOCK_SIZE> *> handleStorageNodes_ {};
    int32_t currentHandleStorageIndex_ {-1};
    int32_t handleScopeCount_ {0};
    JSSpanHandle *spanHandle_ {nullptr};
    ScopedCallInfo *scopedCallInfo_ {nullptr};

    // Run-time state
    bool stableArrayElementsGuardians_ {true};
    InternalCallParams *internalCallParams_ {nullptr};

    // GLUE members start, very careful to modify here
    GlobalEnvConstants globalConst_;  // Place-Holder
    PropertiesCache *propertiesCache_ {nullptr};
    EcmaGlobalStorage *globalStorage_ {nullptr};

    JSTaggedValue globalObj_ {JSTaggedValue::Hole()};

    // Uses to forward a0 argument (functional object) to InvokeHelper
    JSTaggedValue functionalObject_;
    JSTaggedValue invocationLexicalEnv_;

    friend class EcmaHandleScope;
    friend class JSSpanHandle;
    friend class ScopedCallInfo;
    friend class GlobalHandleCollection;
};
}  // namespace ark::ecmascript

#endif  // ECMASCRIPT_JS_THREAD_H
